#include <unistd.h> 

#include<stdio.h>      /* 标准输入输出库 */  
#include<stdlib.h>     /* 标准库函数定义 */  
#include<unistd.h>     /* Unix 标准函数定义 */  
#include<sys/types.h>   
#include<sys/stat.h>     
#include<fcntl.h>      /* 文件控制定义 */  
#include<termios.h>    /* PPSIX 终端控制定义 */  
#include<errno.h>      /* 错误号定义 */  

#include "serialport.h"


namespace Serial_Port {

	class SerialPort::Impl {
	private:
		static const int INVALID_HANDLE_VALUE = -1;

		int port = INVALID_HANDLE_VALUE;
		int system_error_code;
		SerialPort* parent;

	public:
		char port_name[PORT_NAME_MAX_LEN] = "";
		Impl(SerialPort* p) {
			parent = p;
		}
		~Impl() {
			closePort();
		}

		inline bool isPortOpen() {
			if (port == INVALID_HANDLE_VALUE)
			{
				return false;
			}
			else {
				return true;
				//todo: add more check here
			}
		}

		//少一个setbaud
		error_code setPort(char const* ch)
		{
			if (isPortOpen()) {
				return set_name_while_port_opened;
			}
			else {
				for (int i = 0; i < PORT_NAME_MAX_LEN; i++) {
					port_name[i] = ch[i];
					if (ch[i] == '\0') return done;
				}
				return set_name_too_long;
			}
		}
		error_code openPort() {
			if (isPortOpen()) {
				return redo;
			}

			port = open(port_name, O_RDWR | O_NOCTTY | O_NONBLOCK | O_NDELAY);

			if (INVALID_HANDLE_VALUE == port) {
				return open_port_fail;
			}
			if (fcntl(port, F_SETFL, 0) < 0) {
				closePort();
				return system_error;
			}
			if (0 == isatty(STDIN_FILENO)) {
				closePort();
				return open_port_fail;
			}

			struct termios options;
			if (tcgetattr(port, &options) != 0) {
				closePort();
				return system_error;
			}
			cfsetispeed(&options, B38400);
			cfsetospeed(&options, B38400);

			options.c_cflag |= CLOCAL;
			options.c_cflag |= CREAD;
			options.c_cflag &= ~CRTSCTS;
			options.c_cflag |= CS8;
			options.c_cflag &= ~PARENB;
			options.c_iflag &= ~INPCK;
			options.c_cflag |= CSTOPB;
			options.c_oflag &= ~OPOST;
			options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);


			options.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
			options.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
			options.c_oflag &= ~(OPOST);
			options.c_cflag &= ~(CSIZE | PARENB);
			options.c_cflag |= CS8;

			options.c_cc[VTIME] = 0;
			options.c_cc[VMIN] = 0;


			tcflush(port, TCIFLUSH);

			if (tcsetattr(port, TCSANOW, &options) != 0) {
				closePort();
				return system_error;
			}
			return done;
		}

		error_code send(byte const* b, int length) {
			if (isPortOpen() == false) {
				return send_while_port_closed;
			}
			int send_len = 0;
			send_len = write(port, b, length);
			if (send_len == length) {
				return done;
			}
			else {
				tcflush(port, TCOFLUSH);
				return fail;
			}
		}



		error_code recv(byte* b, int& byte_received, int max_length) {
			if (isPortOpen() == false) {
				return recv_while_port_closed;
			}
			int recv_count = 0;
			recv_count = read(port, b, max_length);
			if (recv_count < 0) {
				tcflush(port, TCOFLUSH);
				byte_received = 0;
				return fail;
			}
			else {
				byte_received = recv_count;
				return done;
			}
		}

		error_code closePort() {
			if (port != INVALID_HANDLE_VALUE) {
				if (close(port) < 0) {
					//system_error_code = GetLastError();
					return system_error;
				}
				else {
					port = INVALID_HANDLE_VALUE;
					return done;
				}
			}
			else {
				return redo;
			}
		}



	};

	SerialPort::SerialPort() :pimpl(std::make_unique<Impl>(this)) {}
	SerialPort::~SerialPort() = default;
	
	bool SerialPort::isRunning() {
		return pimpl->isPortOpen();
	}
	error_code SerialPort::setPort(char const* ch) {
		return pimpl->setPort(ch);
	}
	//setbaud 还没有加入
	error_code SerialPort::openPort() {
		return pimpl->openPort();
	}
	error_code SerialPort::closePort() {
		return pimpl->closePort();
	}
	error_code SerialPort::send(byte const* b, int length) {
		return pimpl->send(b, length);
	}
	error_code SerialPort::recv(byte* b, int& byte_received, int max_length) {
		return pimpl->recv(b, byte_received, max_length);
	}

	//这是什么？
	int SerialPort::setPort(int num_in_table) {
		if (num_in_table > sizeof(try_port) / sizeof(try_port[0])) {
			return try_table_max;
		}
		else {
			return pimpl->setPort(try_port[num_in_table]);
		}
	}
	char const* SerialPort::getPortCurrent() {
		return  pimpl->port_name;
	}
}